/**
  ETFAna project, Anyang Normal University && IMP-CAS
  \class ETFVMEUnpacker
  \brief unpacker for raw data collected by VME daq
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2021-09-27
  \date 2024-01-09 last modified
  \attention
  changelog
  <table>
  <tr>  <th>Date        <th>Author      <th>Description                    </tr>
  <tr>  <td>2021-09-27   <td>Asia Sun   <td>created                        </tr>
  <tr>  <td>2023-10-27   <td>Asia Sun   <td>become more adaptive           </tr>
  </table>

  \copyright Copyright (c) 2021-2024 Anyang Normal U. && IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <climits>
// #include <regex>
#include <TClonesArray.h>
#include <TTree.h>
#include "ETFRun.h"
#include "vme_event_head.h"
#include "ETFVMERawEvent.h"
#include "ETFVMEEvent.h"
#include "ETFVMEUnpacker.h"
#include "ETFHPTDCCh.h"
#include "ETFAmpCh.h"
#include "ETFParaManager.h"
#include "ETFDetectorManager.h"
#include "ETFYaml.h"
#include "ETFReadShm.h"
#include "ETFMsg.h"

using std::cout;
using std::endl;

#define er ETFMsg::Error

ETFVMEUnpacker::ETFVMEUnpacker(int id) : ETFUnpacker(kVME, id),
fEvent(new ETFVMEEvent(id)), fRawEvent(0), fData{0}, fPos(0), fIndex0(-1),
fBlockCnt(0), fDisplayComment(true), fCommentStatus(0), fXDebug(false){
  for(int i = NMAXSLOT; i--;) fSlots[i] = fChMap[i] = fCardId[i] = -1;
  for(int i = NMAXTYPE; i--;) fCardCnt[i] = 0;
  fRawEvent = fEvent->raw();
} // end ctor

ETFVMEUnpacker::~ETFVMEUnpacker(){
  if(fEvent) delete fEvent;
} // end the dtor

void ETFVMEUnpacker::Init(){
  Register();
  // register slots //
  const ETFYaml &cc = *ETFParaManager::Instance()->GetChIdPara();
  int type = 0;
  const char *tt[] = {"v830", "v785", "v1190", "v1290A", "v792N", "v965"};
  cout << "\033[1m--------------- Number of VME boards ---------------" << endl;
  for(const char *t : tt) cout << t << '\t';
  cout << endl;
  for(const char *t : tt){
    if(cc[t]) for(auto &s : cc[t]){
      const int slot = s[0].as<int>();
      if(slot >= NMAXSLOT) er("ETFVMEUnpacker", "Init: slot id too large: %d", slot);
      fSlots[slot] = type;
      fChMap[slot] = s[1].as<int>(); // chid0
      if(!strcmp(t, "v785")) fRawEvent->AddADC();
      if(!strcmp(t, "v792N") || !strcmp(t, "v965")) fRawEvent->AddQDC();
      fCardId[slot] = fCardCnt[type];
      fCardCnt[type]++;
    } // end if
    cout << fCardCnt[type] << '\t';
    type++;
  } // end for
  cout << "\033[0m" << endl;
} // end member function Init

void ETFVMEUnpacker::Register(){
  TTree *tree = ETFRun::Instance()->GetTree();
  tree->Branch(Form("vme%d", fId), fEvent);
  tree->Branch(Form("tchvme%d", fId), &fEvent->fHPTDCChannels);

  // sprintf(name, "rawvme%d", fId);
  // fTree->Register(name, fEvent->fRawEvent);
  // sprintf(name, "achvme%d", fId);
  // fTree->Register(name, fEvent->fAmplitudeChannels);
} // end member function Register

void ETFVMEUnpacker::Reset(){
  // DO NOT CLEAR fData HERE - for it contains all events of a whole block
  // memset(fData, 0, sizeof(fData));
  fEvent->Reset();
} // end member function Reset

//----------------------------VME---------------------------------------------//
//---------------------------UNPACK-------------------------------------------//
//----------------------------CODE--------------------------------------------//

// get one event from offline data file to memory (fData)
int ETFVMEUnpacker::GetOneEvent(FILE *fp){
  fPos += fEvent->fEventHead.event_length; // update fPos to next event
  // read the block
  if(fBlockHead.finished()){ // the current block has been read
    if(fread(fData, sizeof(int), kBLKSIZE, fp) <= 0){ feof = true; return UINT_MAX; }
    fSizeCnt += kBLKSIZE * sizeof(int);
    // read comment //
    string cc; const char *c = (const char *)fData; cc.assign(c, 512);
    if(cc.find(" TIME => ") != string::npos){ // comment
      if(fDisplayComment){ //  && regex_search(cc.begin(), cc.end(), std::regex("\\S+"))
        cout << "\nVME data file comment:\n\033[36;1m";
        // cout << cc << endl;
        for(int i = 0; i < 512; i++){
          if('\n' == cc[i]) break; // end of line
          if(cc[i]) cout << cc[i];
        } // end if
        cout << "\033[0m" << endl;
      } // end if display comment
      if(cc.find("START TIME => ") != string::npos) return GetOneEvent(fp);
      if(cc.find("STOP TIME => ") != string::npos) return -1;
    } // end if
    // accept the block as a regular data block //
    fBlockHead.setData(fData); // assign the block header
    fBlockCnt++;
    fPos = 3; // the first 3 words has been read
    if(fXDebug) fBlockHead.print();
  } // end if(Read block)

  vme_event_head &ev_h = fEvent->fEventHead;
  // [0-1-2]: event_length-event_index-vmeReadCount
  ev_h.event_length = fData[fPos];
  ev_h.index = fData[fPos + 1];
  ///////////////////////////////////////////////////////////
  // -- Mar 7, 2024: we have to add this two sentences BACK
  // for compatibility with those data (e.g., jul2018) that
  // have vme events whose index starts from 1, instead of 0
  if(fIndex0){
    if(1 == fIndex0) ev_h.index -= fIndex0;
    else if(-1 == fIndex0) fIndex0 = ev_h.index;
  } // end if
  ///////////////////////////////////////////////////////////
  ev_h.vmeReadCount = fData[fPos + 2];
  fBlockHead.current_event++;

  if(fReadShm) WriteOneEvent();

  return ev_h.index;
} // end member function GetOneEvent(FILE *fp)

// get one event from online data stream to daq
// NOTE that stream always points to a new EVENT, NOT a new BLOCK
int ETFVMEUnpacker::GetOneEvent(int *data){
  memcpy(fData, data, data[0]*sizeof(int));
  fPos = 0; // the reading position

  vme_event_head &ev_h = fEvent->fEventHead;
  // [0-1-2]: event_length-event_index-vmeReadCount
  ev_h.event_length = fData[fPos];
  ev_h.index = fData[fPos + 1];
  ///////////////////////////////////////////////////////////
  // -- Mar 7, 2024: we have to add this two sentences BACK
  // for compatibility with those data (e.g., jul2018) that
  // have vme events whose index starts from 1, instead of 0
  if(-1 == fIndex0) fIndex0 = ev_h.index;
  if(1 == fIndex0) ev_h.index -= fIndex0;
  ///////////////////////////////////////////////////////////
  ev_h.vmeReadCount = fData[fPos + 2];
  if(fDebug) ev_h.print();

  // cout << "-------: VME index: " << fCurrentEventIndex << endl; // DEBUG
  return ev_h.index;
} // end member function GetOneEvent(int *data)

// write one event to dst - simulate daq to test online applications in ETFAna
void ETFVMEUnpacker::WriteOneEvent(){
  if(!fReadShm) er("ETFVMEUnpacker", "WriteOneEvent: fReadShm not assigned.");
  fReadShm->write(&fData[fPos]);
} // end member function ETVMEUnpacker

// do the rest of the unpacking after calling GetOneBlock(). Since 1 block may
// contain several events, Unpack() should be looped for all the events in the
// block. This should be steered in the corresponding ETFOfflineSource::ReadEvent()
// note that the method is called by ETFOfflineSource::ReadEvent(), where Unpack()
// would be bypassed for failure of GetOneBlock()
bool ETFVMEUnpacker::Unpack(){
  fCurrentEventIndex = fEvent->fEvIndex = fEvent->fEventHead.index;
  // decode a VME event
  UserDecode();

  if(fDebug) Print();
  fEventCnt++;

  return true;
} // end member function Unpack

// customizable user event-decoding for a certain vme-config
// user to implement this method should be aware that the data to be decoded
// starts from fData[fPos+3] to fData[fPos+fEvent->fEventHead.event_length]
void ETFVMEUnpacker::UserDecode(){
  static const int NMAXCH = ETFDetectorManager::CH_MAX;
  static ETFHPTDCCh *tchMap[NMAXCH]; // for assigning HPTDC edges
  // static map<int, ETFHPTDCCh *> tchMap; // for assigning HPTDC edges
  for(auto &t : tchMap) if(t) t = nullptr;
  fUpkSta = {.card = -1, .pos = -1, .slot = -1, .chid0 = -1};

  vme_event_head &ev_h = fEvent->GetEventHead();
  const int ev_len = ev_h.event_length;
  TClonesArray *tchs = fEvent->GetHPTDCChannels();
  TClonesArray *achs = fEvent->GetAmplitudeChannels();
  // const auto &adc = fRawEvent->adc();
  // const auto &qdc = fRawEvent->qdc();
  ETFHPTDCCh *tch = nullptr;

  int nsca = 0;
  double amp = -999.;
  int chId = -1, chid = -1; // chid: board-local channel id
  // scalers for counting purposes //
  auto &psca = fEvent->GetPSca(), &sca = fEvent->GetSca();
  auto &dsca = fEvent->GetDSca();
  int &card = fUpkSta.card, &pos = fUpkSta.pos;
  for(int i = 3; i < ev_len; i++){ // loop over channels in an event
    ETFAmpCh *ach = nullptr;
    const int chData = fData[fPos+i];
    UpdateStatus(chData); // tell unpack status
    const int chid0 = fUpkSta.chid0, slot = fUpkSta.slot;
    bool ov = false, un = false; // ov: overflow, un: underflow
    int rg = false; // -1: solo range; 0: high; 1: low; for dual range plugin, e.g., v965

    switch(card){
      case kV830:
        if(0 == pos){ // v830 header
          nsca = 0;
          pos = 4; // enter data zone
          if(fXDebug) cout << "v830 header encountered" << endl;
          continue; // header
        } // end if v830 header
        psca[nsca] = sca[nsca]; sca[nsca] = chData;
        dsca[nsca] = sca[nsca] - psca[nsca];
        static bool warn = true;
        if(warn && dsca[nsca] < 0){ // DEBUG
          ETFMsg::Info("ETFVMEUnpacker", "UserDecode: minus dsca: dsca[%d]: %d",
            nsca, dsca[nsca]);
          warn = false;
        } // end if
        if(16 == ++nsca){ pos = 1; card = -1; } // v830 ended
        break;
      case kV785: case kV965: case kV792N:
        switch(chData>>24 & 0x7){ // header: [24-26]
          case 2: // data header
            pos = 0;
            if(fXDebug){
              cout << "Header for QDC/ADC, slot: " << slot;
              cout << "\tcrate No.: " << (chData>>16 & 0xFF);
              cout << "\tnof ch: " << (chData>>8 & 0x3F) << endl;
            } // end if(debug)
            break;
          case 4: // data trailer
            pos = 1;
            if(fXDebug){
              cout << "Trailer for QDC/ADC, slot: " << slot;
              cout << "\tevent counter: " << (chData & 0xFFFFFF) << endl;
            } // end if
            break;
          case 0: // data channel
            pos = 4;
            ov = chData >> 12 & 0x1; // whether overflow
            un = chData >> 13 & 0x1; // whether underflow
            rg = -1;
            if(kV785 == card || kV965 == card) // 32 channels
              chid = chData >> 16 & 0x1F; // [16-20]: channel Id
            else if(kV792N == card) // 16 channels
              chid = chData >> 17 & 0xF; // [17-20]: channel Id
            if(kV965 == card) chId = (chid>>1) + chid0; // dual range - 16 real chs
            else chId = chid + chid0;
            amp = (chData & 0xFFF) + ETFRawChannel::rand0_1(); // [0-11]: channel data
            // to tell if the 1st dual range ch has been assigned
            if(kV965 == card) ach = fEvent->GetAChByChId(chId);
            if(!ach){ // new channel
              ach = dynamic_cast<ETFAmpCh *>(achs->ConstructedAt(achs->GetEntriesFast()));
              ach->Reset();
              ach->SetChannelId(chId); ach->SetIsPXI(false); ach->SetIsT(false);
            } // end if
            if(kV965 == card){
              rg = chData >> 16 & 0x1; // 0: high range, 1: low range
              // rg true: low range: x8 gain; recover to cmp with high range
              if(1 == rg) amp /= 8.;
            } // end if
            ach->SetAmplitude(amp, rg, ov, un);
            // if(kV785 == card) (*adc[fCardId[slot]])[chid] = amp;
            // if(kV965 == card || kV792N == card) (*qdc[fCardId[slot]])[chid] = amp;
            break;
          default: break;
        } // end switch(header)
        break;
      case kV1190: case kV1290A: // mTDC
        switch(pos){
          case 0: // global header
            if(fXDebug){
              cout << "HPTDC Global Header for v1x90, slot: " << slot << endl;
              cout << "Event count: " << (chData>>5 & 0x3FFFFF) << endl;
              getchar();
            } // end if
            break;
          case 1: // global trailer
            if(fXDebug){ // global trailer
              cout << "HPTDC Global Trailer for v1x90, slot: " << slot << endl;
              cout << "Word count: " << (chData>>5 & 0xFFFFF) << endl;
              getchar();
            } // end global trailer
            break;
          case 2: // group header
            if(!fEvent->GetBunchID()){
              fEvent->SetBunchID(chData & 0xFFF);
              // fEvent->SetEventID(chData>>12 & 0xFFF);
            } // end if
            if(fXDebug){
              cout << "HPTDC group header for v1x90, slot: " << slot << endl;
              cout << "\tbunchId: " << (chData & 0xFFF);
              cout << "\teventId: " << (chData>>12 & 0xFFF) << endl;
              cout << "TDCId: " << (chData>>24 & 0xF);
              cout << "\theadMark: " << (chData>>28 & 0xF) << endl;
              getchar();
            } // end group header
            break;
          case 3: // group trailer
            if(fXDebug){
              // exclude the word count of header and trailer
              cout << "TDC group trailer for v1x90, slot: " << slot << endl;
              cout << ((chData & 0xFFF) - 2) << " hits." << endl; // DEBUG
              cout << "wordCnt: " << (chData & 0xFFF);
              cout << "\teventId: " << (chData>>12 & 0xFFF) << endl;
              cout << "TDCId: " << (chData>>24 & 0xF);
              cout << "\theadMark: " << (chData>>28 & 0xF) << endl;
              getchar();
            } // end group trailer
            break;
          case 4: // data zone
            if(kV1190 == card) chId = chData>>19 & 0x7F;
            if(kV1290A == card) chId = chData>>21 & 0x1F;
            chId += chid0;
            if(chId < NMAXCH){
              if(!tchMap[chId]){
                tchMap[chId] = tch = static_cast<ETFHPTDCCh *>(
                  tchs->ConstructedAt(tchs->GetEntriesFast()));
                tch->Reset(); tch->SetChannelId(chId); tch->SetIsPXI(false);
              } // end if(new chid)
              if(chData>>26 & 0x1) tchMap[chId]->AddTrailingEdgeH(chData & 0x7FFFF);
              else tchMap[chId]->AddLeadingEdgeH(chData & 0x7FFFF);
            } // end if(chid is valid)
            break;
          default: break; // should never reach here
        } // end switch
        break;
      default: // unknown or intentionally ignored slots
        // ETFMsg::Warn("ETFVMEUnpacker", "UserDecode: unknown card type: card: %d", card);
        break; // should never reach here
    } // end switch(card)
  } // end for over channels in an event
} // end member function UserDecode

// update unpack status fUpkSta
void ETFVMEUnpacker::UpdateStatus(int ch){
  if(kV830 == fUpkSta.card) return; // v830 data zone spans the whole word

  const int slot = ch>>27 & 0x1F; // [27,31]: slot number
  // v1x90 mTDC: HPTDC global header //
  if(8 == slot){
    const int geo = ch & 0x1F; // [0-4]: plugin id (aka. mTDC slot No.)
    if(geo < NMAXSLOT && fSlots[geo] >= 0){ // HPTDC global header
      fUpkSta.card = fSlots[geo];
      if(kV1190 != fUpkSta.card && kV1290A != fUpkSta.card)
        er("ETFVMEUnpacker", "UnpackStatus: abnormal data");
      fUpkSta.pos = 0; // 0: GL_H, 1: GL_T; 2: GR_H, 3: GR_T; 4: DATA
      fUpkSta.slot = geo;
      fUpkSta.chid0 = fChMap[geo];
      return;
    } // end if(geo)
    else{ // genuine slot 8, but not mtdc
      fUpkSta.card = -1;
      fUpkSta.pos = -1;
    }
  } // end if HPTDC global header
  // inside HPTDC //
  if(kV1190 == fUpkSta.card || kV1290A == fUpkSta.card){
    switch(slot){
      case 16: fUpkSta.pos = 1; break; // global trailer
      case 1: fUpkSta.pos = 2; break; // group header
      case 3: fUpkSta.pos = 3; break; // group trailer
      case 0: fUpkSta.pos = 4; break; // data zone
      default: break; // should never happen
    } // end switch
    return;
  } // end if inside HPTDC

  // from now on slot is the genuine "slot" - qdc, adc, etc. //
  if(slot < NMAXSLOT && fSlots[slot] >= 0){
    if(1 == fUpkSta.pos || -1 == fUpkSta.pos){
      fUpkSta.card = fSlots[slot];
      fUpkSta.slot = slot;
      fUpkSta.pos = 0;
      fUpkSta.chid0 = fChMap[slot];
    } // end inner if
  } // end if
  else{
    fUpkSta.initialize();
    // er("ETFVMEUnpacker", "UpdateStatus: unregistered slot: %d", slot);
    // exit(1);
  } // end else
} // end member function UpdateStatus

// Print the unpacked data of the current event
void ETFVMEUnpacker::Print(){
  ETFUnpacker::Print();
  fEvent->Print();
  fRawEvent->Print();
} // end member function Print

void ETFVMEUnpacker::PrintSummary(){
  cout << "  VME unpacker " << fId << ": ";
  cout << "\033[35;1m" << filename() << "\033[0m unpacked, ";
  cout << "\033[1m" << fEventCnt << "\033[0m events from \033[1m" << fBlockCnt
       << "\033[0m blocks analysed" << endl;
  
  cout << "\033[35;1mCurrent Event Index: " << fCurrentEventIndex << "\033[0m" << endl;
} // end member function PrintSummary
